package com.ants.boot.core.security;

import com.ants.boot.core.security.provider.CustomAuthenticationProvider;
import com.ants.boot.core.security.provider.PhoneAuthenticationProvider;
import com.ants.boot.core.security.filter.LoginAuthenticationFilter;
import com.ants.boot.core.security.handle.*;
import com.ants.boot.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;

/**
 * @author 蚂蚁会花呗
 * @date 2022/2/15 17:26
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,jsr250Enabled = true,securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private LoginSuccessHandle loginSuccessHandle;

    @Autowired
    private LoginFailureHandler loginFailureHandler;

    @Autowired
    private LogoutClearHandler logoutClearHandler;

    @Autowired
    private UnAuthorizedEntryPointHandler unAuthorizedEntryPointHandler;

    /**
     * 手机验证码认证
     */
    @Autowired
    private PhoneAuthenticationProvider phoneAuthenticationProvider;

    /**
     * 传统账号密码方式认证
     */
    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;


    /**
     * 密码加密
     * 哈希加密算法。
     * SHA-256+随机盐+密钥进行加密
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * configure分别配置多种登录验证方式
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //用户名和密码登陆
        auth.authenticationProvider(customAuthenticationProvider);
        //短信验证码登录
        auth.authenticationProvider(phoneAuthenticationProvider);
    }

    /**
     * 白名单
     *
     * 自定义的Jwt Token校验过滤器
     * 这里设置过滤器放行的URL，因为使用了过滤器，security 的拦截可以设置为都不拦截
     * 统一由过滤器去拦截URL
     * @return
     */
    @Bean
    public RequestJwtAuthenticationFilter authenticationTokenFilterBean() {
        RequestJwtAuthenticationFilter filter = new RequestJwtAuthenticationFilter();
        filter.setPermissiveUrl(
                "/img/**",
                "/upload/**",
                "/ants/login",
                "/ants/captcha",
                "/ants/send_captcha",
                "/logout",
                "/doc.html",
                "/swagger-resources",
                "/swagger-resources/configuration/ui",
                "/v2/api-docs",
                "/webjars/**",
                "/wx/auth/*",
                "/favicon.ico",
                "/websocket/**",
                "/druid/**");
        return filter;
    }

    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    /**
     * 具体Security 配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        LoginAuthenticationFilter filter = new LoginAuthenticationFilter(redisUtils);
        filter.setAuthenticationManager(authenticationManager());
        //登录成功
        filter.setAuthenticationSuccessHandler(loginSuccessHandle);
        //登录失败
        filter.setAuthenticationFailureHandler(loginFailureHandler);
        http.addFilterBefore(filter,UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(authenticationTokenFilterBean(),UsernamePasswordAuthenticationFilter.class)
                //禁用session，因为是token认证登录
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //禁用csrf
                .and().csrf().disable()
                .authorizeRequests()
                .antMatchers("/**").permitAll()
                .anyRequest()
                .authenticated()
                //禁用默认登录，改为使用自定义登录
                .and().formLogin().disable()
                //无权限以及认证失败的情况
                .exceptionHandling().authenticationEntryPoint(unAuthorizedEntryPointHandler)
                //退出登录，由于是jwt token无状态登录，退出登录由前端直接清除token即可，后端不做处理。
                //如果需要后端处理，可以引用redis、退出时直接清除redis即可，或者在token中加入版本号等情况
                .and().logout().logoutUrl("/user/logout").addLogoutHandler(logoutClearHandler).logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
    }

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }
}
